#include "../../include/scheduler.h"
#include "../../include/kc.h"
#include "../../include/kasm.h"
#include "../../include/terminal.h"
#include "../../include/syscalls.h"
#include "../../include/sleep.h"
#include "../../include/memoryPaging.h"
#include "../../include/fileSystem/inode.h"
#include "../../include/fileSystem/fileDescriptor.h"

void * buildStackFrame(processFunc process, int argc, char ** argv,
	int stackBottom);
int getPid();
void initProcesses(void);
void cascadeKill(int index);
void rCascadeKill(int index);
int destroyP(int index);

typedef struct sch {
	int indexLastSibling;
	int indexNextSibling;
	int indexChild;
	int indexParent;
	int nextIndex;
	int lastIndex;
}scheduler_t;

process_t processes[MAX_CANT_PROC];
scheduler_t scheduler[MAX_CANT_PROC];

int currPid = 0;
int nextPid = 0;
int cantProc = 0;
int currProc = 0;

int
createProcess(char * name, processFunc process, char isBackground,
	int argc, char ** argv, unsigned char priority, char tty, int stackLen){
	int auxindex;

	if(cantProc < MAX_CANT_PROC){
		strcpy(processes[cantProc].name, name);
		processes[cantProc].background = isBackground;
		processes[cantProc].priority = priority;
		processes[cantProc].pid = getPid();
		processes[cantProc].tty = tty;
		processes[cantProc].state = READY;
		processes[cantProc].stackStart = pageMalloc(&stackLen);
		processes[cantProc].stackLen = stackLen;
		processes[cantProc].ESP = buildStackFrame(process, argc, argv,
			(int)(processes[cantProc].stackStart + processes[cantProc].stackLen-1));
		processes[cantProc].cpuUsage = 0;
		processes[cantProc].tics = 0;
		scheduler[cantProc].indexParent=currProc;
		scheduler[cantProc].indexChild=-1;
		scheduler[cantProc].indexNextSibling=cantProc;
		scheduler[cantProc].indexLastSibling=cantProc;
		scheduler[cantProc].lastIndex=cantProc;
		scheduler[cantProc].nextIndex=cantProc;

		processes[cantProc].rootDir = getInode("/");
		if (processes[currProc].currDir == NULL)
			processes[cantProc].currDir = copyInode(processes[cantProc].rootDir);
		else
			processes[cantProc].currDir = copyInode(processes[currProc].currDir);

		//verifica si no es init, sino luego se genera un ciclo infinito
		if(cantProc!=0) {
			//ingresa atras de la lista de procesos al generado
			scheduler[cantProc].nextIndex=0;
			scheduler[scheduler[cantProc].lastIndex=scheduler[0].lastIndex].nextIndex=cantProc;
			scheduler[0].lastIndex=cantProc;
			auxindex = currProc;
			//une al proceso con el "arbol" de su familia
			if(scheduler[auxindex].indexChild==-1)
				scheduler[auxindex].indexChild=cantProc;
			else {
				auxindex=scheduler[auxindex].indexChild;
				scheduler[cantProc].indexLastSibling=scheduler[auxindex].indexLastSibling;
				scheduler[auxindex].indexLastSibling=cantProc;
				scheduler[scheduler[cantProc].indexLastSibling].indexNextSibling=cantProc;
				scheduler[cantProc].indexNextSibling=auxindex;
			}
		}

		//una vez que llene el stack, desactivo la pagina del proceso nuevo
		//el scheduler lo va a activar y desactivar cada vez que sea necesario
		deactivatePage(processes[cantProc].stackStart);

		return processes[cantProc++].pid;
	}

	return 0;
}

void *
buildStackFrame(processFunc process, int argc, char ** argv, int stackBottom) {

	//puntero a la tabla de punteros a los argumentos
	char ** argvTable = (char**)(stackBottom - sizeof(char*) * argc);

	//puntero a cada argumento. arranca justo despues de la tabla
	char * argDir = (char*)argvTable;

	int i;
	for (i = 0; i < argc; i++) {
		int len = strlen(argv[i]) + 1;
		//guardo espacio para que me entre el argumento
		argDir -= len;
		//le asigno la direccion del argumento a la tabla
		argvTable[i] = argDir;
		//copio en el espacio guardado el argumento
		memcpy(argvTable[i], argv[i], len);
	}

	//le asigno al puntero al stackFrame suficiente espacio, a partir
	//de la posicion del ultimo parametro
	stackFrame_t * frame = (stackFrame_t *)(argDir - sizeof(stackFrame_t));

	frame->argc = argc;
	frame->argv = argvTable;

	//lo seteo en 0 por si lo intenta referenciar
	frame->EBP = 0;

	//seteo el instruction pointer para que apunte al comienzo del proceso
	frame->EIP = (int)process;

	//asigno al segmento de codigo el segundo elemento de la gdt
	frame->CS = 0x08;

	//seteo el flag IF para que se produzcan interrupciones
	frame->EFLAGS = 0x1 << 9;

	//seteo la direccion de retorno (RET) del proceso para que vaya a
	// la funcion encargada de eliminar el proceso
	frame->retaddr = cleanProcess;

	return (void *)frame;
}

void
initScheduler(void) {
	initProcesses();
	initSleep();

	createProcess("init", init, 1, 0, 0, CANT_PRIO, 0, 0x2000);

	//valores iniciales para que se diferencie el primer switcheo de
	//tarea del resto
	currPid = 0;
	currProc = 0;
}

int
getPid() {
	return ++nextPid;
}

static void
computeStatistics(void) {
	static int cantTics = 0;

	//aumento la cantidad de tics del proceso
	processes[currProc].tics++;

	//aumento la cantidad de tics para las estadisticas
	cantTics++;

	//cada cierta cantidad de tics, computo las estadisticas
	if ( (cantTics % MAX_STAT_TICS) == 0) {
		int i;
		for(i=0; i < cantProc; i++){
			//calculo el porcentaje de tics usado como uso del cpu
			processes[i].cpuUsage = processes[i].tics * 100 / cantTics;
		}

	}

	if (cantTics >= (MAX_STAT_TICS * 4)) {
		int i;
		for(i=0; i < cantProc; i++){
			//reseteo la cantidad de tics
			processes[i].tics = 0;
		}
		cantTics = 0;
	}

}

void *
schedule(int yieldCall){
	int next, initFlag = 0;

	//desactivo la pagina del proceso que va a perder el procesador
	deactivatePage(processes[currProc].stackStart);

	next=currProc;
	//seteo el proceso que va a salir como que esta esperando, a
	//menos que estuviera bloqueado
	if(processes[currProc].state==RUNNING)
		processes[currProc].state=READY;
	while(1) {

		next=scheduler[next].nextIndex;

		//si next es init, entro al if
		if (next == 0) {
			//si no habia entrado antes, enciendo initFlag. si initFlag esta
			//encendido, quiere decir que di toda la vuelta, y no hay nada
			//para hacer, asi que corro init.
			if (!initFlag)
				initFlag = 1;
			else
				break;
			next=scheduler[next].nextIndex;
		}

		if(processes[next].state==READY) {
			break;
		}
		if(processes[next].state==DEAD) {
			next=destroyP(next);
		}
	}

	//seteo el nuevo proceso actual como que esta corriendo
	processes[next].state = RUNNING;

	currPid = processes[next].pid;
	currProc = next;

	computeStatistics();

	//activo la pagina del proceso que va a obtener el procesador
	activatePage(processes[next].stackStart);

	return processes[next].ESP;
}

/* Elimina un DEAD, cambia el indice al proximo si es necesario, devuelvo el
 * indice ANTERIOR al eliminado*/
int
destroyP(int index) {
	int auxindex, auxchild;

	//devuelvo los inodos de currDir y rootDir
	putInode(processes[index].currDir);
	putInode(processes[index].rootDir);

	int fd;
	//libero todos los filedescriptors que puedan haber quedado abiertos
	for (fd = 0; fd < FD_PER_PROC; fd++) {
		if (processes[index].fd[fd].inode != NULL) {
			putInode(processes[index].fd[fd].inode);
			processes[index].fd[fd].inode = NULL;
		}
	}
	//libero la pagina del proceso
	pageFree(processes[index].stackStart);


	auxindex=scheduler[index].lastIndex;

	if(scheduler[scheduler[index].indexParent].indexChild==index) {
		if(scheduler[index].indexNextSibling==index)
			scheduler[scheduler[index].indexParent].indexChild=-1;
		else
			scheduler[scheduler[index].indexParent].indexChild=scheduler[index].indexNextSibling;
	}
	if(scheduler[index].indexNextSibling!=index) {
		scheduler[scheduler[index].indexNextSibling].indexLastSibling=scheduler[index].indexLastSibling;
		scheduler[scheduler[index].indexLastSibling].indexNextSibling=scheduler[index].indexNextSibling;
	}
	scheduler[scheduler[index].nextIndex].lastIndex=scheduler[index].lastIndex;
	scheduler[scheduler[index].lastIndex].nextIndex=scheduler[index].nextIndex;
	//ahora traigo lo que habia en la ultima posicion para el lugar este
	if(index!=cantProc-1) {
		processes[index]=processes[cantProc-1];
		scheduler[index]=scheduler[cantProc-1];
		if(scheduler[index].indexChild!=-1) {
			auxchild=scheduler[index].indexChild;
			while(scheduler[auxchild].indexParent==cantProc-1) {
				scheduler[auxchild].indexParent=index;
				auxchild=scheduler[auxchild].indexNextSibling;
			}
		}
		if(scheduler[scheduler[index].indexParent].indexChild==cantProc-1)
			scheduler[scheduler[index].indexParent].indexChild=index;

		scheduler[scheduler[index].nextIndex].lastIndex=index;
		scheduler[scheduler[index].lastIndex].nextIndex=index;

		if(scheduler[index].indexNextSibling==cantProc-1)
			scheduler[index].indexNextSibling=scheduler[index].indexLastSibling=index;
		else {
			scheduler[scheduler[index].indexNextSibling].indexLastSibling=index;
			scheduler[scheduler[index].indexLastSibling].indexNextSibling=index;
		}
	}
	cantProc--;

	return auxindex;
}

void
initProcesses(void){

}

int
showPPid() {
	return processes[scheduler[currProc].indexParent].pid;
}

//devuelve 1 si mato el proceso correctamente, 0 si no lo encontro
int
killProc(int pid){
	int i;
	for(i=0; i < cantProc; i++){
		if(processes[i].pid == pid){
			wakeProcess(&processes[scheduler[i].indexParent], pid);
			if(processes[i].state!=DEAD)
				cascadeKill(i);
			return 1;
		}
	}

	return 0;
}

//wrapper de cascadeKill
void
cascadeKill(int index) {
	int bro;

	if(index!=0) {
		bro=scheduler[scheduler[index].indexParent].indexChild;
		//si soy el primer hijo de mi padre
		if(bro==index) {
			if(bro!=scheduler[index].indexNextSibling)
				//uno a mi primer hermano con mi padre
				scheduler[scheduler[index].indexParent].indexChild=scheduler[index].indexNextSibling;
			else
				//no tengo hermanos
				scheduler[scheduler[index].indexParent].indexChild=-1;
		}
		else {
			while(scheduler[bro].indexNextSibling!=index)
				bro=scheduler[bro].indexNextSibling;
		}
		//uno a mi anterior hermano con mi proximo hermano
		scheduler[bro].indexNextSibling=scheduler[index].indexNextSibling;
		//uno a mi proximo hermano, con mi anterior hermano
		scheduler[scheduler[index].indexNextSibling].indexLastSibling=bro;
		//en caso de que haya sido hijo unico, no importa, porque basicamente se va
		//a estar pasando su indice en ambos casos

		rCascadeKill(scheduler[index].indexChild);
		processes[index].state = DEAD;
	}
}

//recursiva que mata hijos
void
rCascadeKill(int index) {
	int aux;
	aux=index;

	if(index==-1) {
		return;
	}
	//mientras que el hermano no sea el primer hijo del padre...
	while(scheduler[aux].indexNextSibling!=index) {
		//mato a sus hijos
		rCascadeKill(scheduler[aux].indexChild);
		//le pego un tiro
		processes[aux].state = DEAD;
		//averiguo donde vive su hermano, y lo voy a buscar
		aux=scheduler[aux].indexNextSibling;
	}
	//al ultimo hijo del padre le llego su hora
	rCascadeKill(scheduler[aux].indexChild);
	processes[aux].state = DEAD;
}

//salva el stack del proceso actual y devuelve el stack para hacer scheduling
void * switchToSchedulingContext(void * esp) {
	static void * schedulingESP;
	//si el currPid es <= 0, es la primera vez que entro, y el esp que me
	//estan enviando es el del kernel. Lo guardo como el schedulingESP.
	if (currPid <= 0) {
		schedulingESP = esp;
		return schedulingESP;
	}
	//salvo el esp del proceso actual
	processes[currProc].ESP = esp;
	//devuelvo el esp para hacer scheduling
	return schedulingESP;
}


void
cleanProcess(void) {
	kill(currPid);
	yield();
}

//cambia el estado del proceso a bloqueado
void
blockProcess(int index, int blockState) {
	if (processes[index].state != DEAD) {
		processes[index].state = BLOCKED;
		processes[index].block = blockState;
	}
}

//busca procesos de la terminal tty que esten bloqueados por read y esten en
//el foreground y los desbloquea
void
unblockProcesses(int tty) {
	int i;
	for(i=0; i < cantProc; i++){
		if (processes[i].state == BLOCKED && processes[i].tty == tty
			&& processes[i].background == 0 && processes[i].block == B_READ) {
			processes[i].state = READY;
			return;
		}
	}
	return;
}

void
wakeProcess(process_t * proc, int srcPid) {
	proc->waitPid = srcPid;	//le digo que pid lo desperto
	if (proc->state == BLOCKED && proc->block == B_WAIT) {
		proc->state = READY;
	}
}

int schedulerGetProcs(process_info_t * procs, int maxProcs){

	int i, j=0, cant;

	if(maxProcs == -1)
		cant = cantProc;
	else
		cant = (maxProcs <= cantProc)? maxProcs:cantProc;

	for(i=0; i < cant; i++){
		if(processes[i].state != DEAD){

			procs[j].pid = processes[i].pid;
			strcpy(procs[j].name, processes[i].name);
			procs[j].priority = processes[i].priority;
			procs[j].tty = processes[i].tty;
			procs[j].background = processes[i].background;
			procs[j].state = processes[i].state;
			procs[j].block = processes[i].block;
			procs[j].cpuUsage = processes[i].cpuUsage;

			j++;
		}
	}

	return j;

}

int
getIndexParent(int index) {
	return scheduler[index].indexParent;
}

int
waitImpl(int waitPid) {
	//este ciclo se hace una vez por cada proceso que trata de despertarme.
	//solo salgo si el pid al que estaba esperando es el que me desperto.
	while (waitPid != processes[currProc].waitPid) {
		//me voy a bloquear hasta que me despierten
		blockProcess(currProc, B_WAIT);
		_sys_yield();
	}
	processes[currProc].state = READY;	//me seteo en ready
	processes[currProc].waitPid = 0; //limpio el waitPid

	return 1;
}

process_t *
getProcByPid(int pid) {
	int i;

	for(i=0; i< cantProc; i++) {
		if(processes[i].pid==pid)
			return &processes[i];
	}

	return NULL;
}

